/**
 * Connect 4   Based on a demo by Yngvi Bjornsson
 */
import java.util.ArrayList;
import java.util.StringTokenizer;
import java.io.*;

import java.awt.*;
import javax.swing.*;

// There are no TODOs in this file, but feel free to explore the code
// and try to understand how the search is driven.

public class Connect4 {
	
	private static JFrame frame;
	private static Hole[][] holes;
	
	private static void createAndShowGUI() {
		// This function sets up and displays the board window
		
        frame = new JFrame("Connect 4");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        JPanel pnl = new JPanel();
        pnl.setPreferredSize(new Dimension(300,300));
        pnl.setLayout(new GridLayout(7,7));
        
        holes = new Hole[6][7];
        for (int i = holes.length-1; i >= 0; --i) {
        	// We count i down as we want e.g. holes[0] to be the
        	// _bottom_ row of holes in the picture
        	for (int j = 0; j < holes[i].length; ++j) {
        		holes[i][j] = new Hole();
        		pnl.add(holes[i][j]);
        	}
        }
        for (char c = 'a'; c <= 'g'; ++c) {
        	JLabel l = new JLabel(String.valueOf(c), JLabel.CENTER);
        	l.setAlignmentY(Component.CENTER_ALIGNMENT);
        	l.setAlignmentX(Component.CENTER_ALIGNMENT);
        	pnl.add(l);
        }
        
        frame.getContentPane().add(pnl);
        frame.pack();
        frame.setVisible(true);
	}

    public static void main( String[] args )
    {

        System.out.println( "Welcome to Connect 4 Demo" );

        State            state          = new State( 6, 7 );
        Search           search         = new Search();
        ArrayList<Move>  moveHistory    = new ArrayList<Move>();
        int              maxSearchDepth = 5;
        
        javax.swing.SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                createAndShowGUI();
            }
        });

        try {
            BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

            display(state);

            // Command loop.
            System.out.print("> ");
            String line = br.readLine();
            while ( line != null ) {
                StringTokenizer st = new StringTokenizer( line );
                if ( st.hasMoreTokens() ) {
                    String command = st.nextToken();

                    if ( command.equals("h") ) {
                        // Print help menu.
                        System.out.println("Options:");
                        System.out.println("  i      : Initialize, new game.");
                        System.out.println("  m <c>  : Make a move, c indicates column (e.g. 'm b').");
                        System.out.println("  r      : Retract last move.");
                        System.out.println("  d <n>  : Set maximum search depth.");
                        System.out.println("  g      : Go, thinking! Find and play (the best) move.");
                        System.out.println("  p      : Print a machine readable representation of the state.");
                        System.out.println("  q      : Quit the program.");
                        System.out.println();
                        System.out.println("    When states are printed, the output also includes (in order)");
                        System.out.println("    whose move it is next, if the state is terminal, what is the");
                        System.out.println("    evaluation value of the state and possible moves.");
                    }
                    else if ( command.equals("i") ) {
                        // Initialize to start position.
                        state.reset();
                        moveHistory.clear();
                        display(state);
                    }
                    else if ( command.equals("m") ) {
                        // Make the provided move (if legal).
                        if ( st.hasMoreTokens() ) {
                            String strMove = st.nextToken();
                            Move move = state.isLegalMove( strMove );
                            if ( move != null ) {
                               state.make( move );
                               moveHistory.add( move );
                               display(state);
                            }
                            else {
                                System.out.print( " => State error, illegal move: " );
                                System.out.println( move );
                            }
                        }
                        else {
                            System.out.println( " => Input error, move missing.");
                        }
                    }
                    else if ( command.equals("r") ) {
                        // Retract a move.
                        if ( !moveHistory.isEmpty() ) {
                            Move move = moveHistory.get( moveHistory.size() - 1 );
                            state.retract( move );
                            moveHistory.remove( moveHistory.size() - 1 );
                            display(state);
                        }
                    }
                    else if ( command.equals("d") ) {
                        // Set the nominal search depth.
                        if ( st.hasMoreTokens() ) {
                            try {
                                maxSearchDepth = Integer.valueOf(st.nextToken());
                            } catch (NumberFormatException e ) {
                                System.out.println( " => Input error, depth not an integer.");
                            }
                        }
                        else {
                            System.out.println( " => Input error, depth missing.");
                        }
                    }
                    else if ( command.equals("g") ) {
                        // Search to find the best move, then play it.
                        if ( !state.isTerminal() ) {
                        	// This is where your search program is called
                            Move move = search.findBestMove( state, maxSearchDepth );
                            if ( move != null ) {
                                state.make( move );
                                moveHistory.add( move );
                                System.out.print( "bestmove " );
                                System.out.println( move.toStr() );
                                display(state);
                                if ( state.isTerminal() ) {
                                    System.out.print( "gameover ");
                                    Result result = state.getResult();
                                    if ( result == Result.Draw ) {
                                        System.out.println( "1/2-1/2" );
                                    }
                                    else if ( result == Result.WinBlack ) {
                                       System.out.println( "1-0" );
                                    }
                                    else if ( result == Result.WinRed ) {
                                        System.out.println( "0-1" );
                                    }
                                    else {
                                        System.out.println("Unknown");
                                    }
                                }
                            }
                            else {
                                System.out.println( " => Search error, returned illegal move." );
                            }
                        }

                    }
                    else if ( command.equals("p") ) {
                    	// Print the state in machine readable form. This
                    	// is used when a server is driving the application
                    	switch (state.getPlayerToMove()) {
                    		case Black: System.out.print("B"); break;
                    		case Red:   System.out.print("R"); break;
                    	}
                    	for (State.Square[] row : state.getBoard()) {
                    		for (State.Square sqr : row) {
                    			switch (sqr) {
                    				case Empty: System.out.print("."); break;
                    				case BlackDisk: System.out.print("B"); break;
                    				case RedDisk: System.out.print("R"); break;
                    			}
                    		}
                    	}
                    	System.out.println();
                    }
                    else if ( command.equals("q") ) {
                        System.exit(0);
                    }
                    else {
                        System.out.println(" => Unknown command.");
                    }

                }
                
                // Update the GUI
                State.Square[][] board = state.getBoard();
                for (int i = 0; i < board.length; ++i) {
                	for (int j = 0; j < board[i].length; ++j) {
                		holes[i][j].setState(board[i][j]);
                	}
                }
                
                System.out.print("> ");
                line = br.readLine();
            }

        } catch (IOException ioe) {
           System.out.println("IO error reading standard input.");
           System.exit(1);
        }
    }
    
    public static void display(State s)
    {
    	// Print a state to stdout, for debugging
    	
        ArrayList<Move> moves = new ArrayList<Move>();
        State.Square[][] board = s.getBoard();

        for ( int row=board.length-1; row>=0; --row ) {
            System.out.print( ' ' );
            System.out.print( row+1 );
            System.out.print( ' ' );
            for ( int col=0; col < board[row].length; ++col ) {
                char c = ' ';
                switch ( board[row][col] ) {
                    case Empty:     c='.'; break;
                    case RedDisk:   c='o'; break;
                    case BlackDisk: c='x'; break;
                }
                System.out.print(c);
            }
            System.out.println();
        }
        System.out.print( "   " );
        for ( int col=0; col < board[0].length; ++col ) {
             System.out.print( "" + "abcdefghijklmnopqrstuvwxyz".charAt(col) );
        }
        System.out.println();
        System.out.println( s.getPlayerToMove() == Player.Black ? " Black" : " Red" );
        System.out.print( " Term.: ");
        System.out.println( s.isTerminal() );
        System.out.print( " Eval.: ");
        System.out.println( s.getEvaluation() );
        System.out.print( " Moves: ");
        moves = s.getActions();
        for ( Move move : moves ) {
            System.out.print( ' ' );
            System.out.print( move.toStr() );
        }
        System.out.println();
    }

}

/* A helper class that represents a hole displayed in the GUI */
class Hole extends JPanel {
	
	private State.Square st;
	
	public Hole() {
		st = State.Square.Empty;
	}
	
	public void setState(State.Square newSt) {
		st = newSt;
		repaint();
	}

	public void paintComponent(Graphics g) {
		super.paintComponent(g);
		switch (st) {
			case Empty: 	g.setColor(Color.lightGray); break;
			case RedDisk:   g.setColor(Color.red);       break;
			case BlackDisk: g.setColor(Color.black);     break;
		}
		g.fillOval(1, 1, getWidth()-2, getHeight()-2);
	}

}

